<?php
/**
 * Copyright (c) 2006-2008, Julien PORTALIER
 * http://featherphp.googlecode.com/
 * 
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 */

/**
 * Main Controller.
 */
class Controller extends View
{
	public    $name;
	public    $action;
	public    $uri;
	public    $here;
	
	public    $load;
	public    $components     = array();
	public    $helpers        = array();
	
	# data
	public    $args;
	public    $params         = array();
	
	# model
	protected $uses           = null;
	public    $model_class;
	
	# view
	protected $layout;
	protected $default_layout = 'default';
	public    $auto_render    = true;
	protected $page_title;
	
	# callbacks
#	protected $before_filter  = array();
#	protected $after_filter   = array();
	
	/**
	 * Contructs the Controller, parses and prepares data, binds callbacks, etc.
	 * 
	 * @todo Support HTTP PUT, by parsing it and populating $_POST.
	 * 
	 * @param $uri    String Absolute URI of current REQUEST.
	 * @param $action String Action to execute. 
	 * @param $args   Array  Passed parameters.
	 */
	function __construct($uri, $action, array &$args=null)
	{
		$this->name        = str_replace('Controller', '', get_class($this));
		$this->action      = $action;
		$this->uri         = $uri;
		$this->here        = FULL_BASE_URL."/$uri";
		$this->model_class = ($this->uses === null) ? Inflector::singularize($this->name) : $this->uses;
		$this->load        = new Load($this);
		
		# parameters
		$this->args   =& $args;
		$this->params =& $this->args[':params'];
		
		# callbacks
#		$this->bind_callback('before_action');
#		$this->bind_callback('after_action');
#		$this->bind_callback('before_render');
#		$this->bind_callback('after_render');
		
		# model class (if any)
		if (!empty($this->model_class)) {
			Load::model($this->model_class);
		}
		
		# view
		View::__construct();
		
		
		# @todo MOVE ALL THE FOLLOWING ELSEWHERE:
		
		# handles POST FIELDS in HTTP PUT
		if ($_SERVER['REQUEST_METHOD'] == 'PUT')
		{
			/*
			$body = '';
			$std_input = fopen('php://input', 'r');
			while ($tmp = fread($std_input, 1024)) {
				$body .= $tmp;
			}
			fclose($std_input);
			*/
			$body  = stream_get_contents(STDIN);
			$_POST = HTTP::decode_postbody($body);
		}
		
		# handles uploaded data in JSON or XML format
		if (!empty($_POST['json'])) {
			$_POST = json_decode($_POST['json'], true);
		}
		elseif (!empty($_POST['xml']))
		{
			Load::libs('xml');
			$_POST = xml::from_string($_POST['xml']);
		}
	}
	
	/**
	 * Handles not found actions. May be overwritten if you want custom support
	 * for this, or if you want to use this as a feature (on the fly page creation,
	 * for instance).
	 */
	function __call($func, array $args)
	{
		throw new Exception(sprintf("Controller '%s' has no such action '%s'.", $this->name, $func), 404);
	}
	
	/**
	 * Magic function to load model_class or Components on request.
	 */
	function __get($attr)
	{
		# model?
		if ($attr == $this->model_class) {
			return $this->$attr = new $attr();
		}
		
		# component?
		try
		{
			$this->load->component($attr);
			return $this->$attr;
		}
		catch(Load_Exception $e) {
			#trigger_error($e->getMessage(), E_USER_ERROR);
		}
		
		# error
		trigger_error("No such attribute '{$this->name}::\${$attr}'.", E_USER_WARNING);
	}
	
	/**
	 * Executes an action.
	 * 
	 * @param $action String[optional]
	 * @param $params Array[optional]
	 */
	function run_action($action=null, array $params=null)
	{
		if (is_null($action)) {
			$action = $this->action;
		}
		
		if ($action == 'action' or strpos($action, '_') === 0) {
			throw new HTTPError(400);
		}
		
		if (is_null($params))
		{
			#$params =& $this->args[':params'];
			$params =& $this->params;
		}
		
		try
		{
			# before action
			$this->before_action();
#			$this->callback('before_action');
#			foreach($this->before_filter as $func) {
#				$this->$func();
#			}
			
			# action
			call_user_func_array(array($this, $action), $params);
			
			# after action
#			foreach($this->after_filter as $func) {
#				$this->$func();
#			}
#			$this->callback('after_action');
			$this->after_action();
		}
		catch (ActiveRecord_Exception_RecordNotFound $e)
		{
			# automagically handles not found errors thrown by ActiveRecord :) 
			$this->render('404');
		}
		
		# render
		if ($this->auto_render) {
			$this->render($action);
		}
	}
	
	/**
	 * Renders a template in a layout.
	 * 
	 * For instance: $this->render('index', 'default', 'json') will render the
	 * template 'index.json.tpl' using the layout 'default.json.tpl'. It defaults to
	 * 'action.format.tpl'.
	 * 
	 * Controller::render() is called automatically. Set Controller::$auto_render
	 * to false if you can't to disable the automatic rendering.
	 * 
	 * Format is defined automatically from the URL pseudo file extension. For
	 * instance index.json will render index.json.tpl using layout json.tpl.
	 * 
	 * @param $action String Render a specific action.
	 * @param $layout String Render action in a specific layout.
	 * @param $format String Render a particular format (eg: html, rss, json, xml, etc.).
	 */
	function render($action=null, $layout=null, $format=null)
	{
		# which action?
		$action = is_null($action) ? $this->action : $action;
		$format = is_null($format) ? $this->args[':format'] : $format;
		
		# which layout?
		if (is_null($layout))
		{
			if (empty($this->layout))
			{
				if (isset($_GET['layout'])) {
					$layout = $_GET['layout'];
				}
#				elseif (HTTP::$is_ajax) {
#					$layout = 'bare';
#				}
#				elseif (isset($this->args['bare']) and $this->args['bare']) {
#					$layout = 'bare';
#				}
				else {
					$layout = $this->default_layout;
				}
			}
			else {
				$layout = $this->layout;
			}
		}
		
		# special vars
		$this->set_for_layout('title', $this->page_title);
		
		# render
#		$this->ignite_view();
		parent::render($action, $layout, $format);
		
		$this->auto_render = false;
	}
	
	/**
	 * Redirects to another page and passes a text message.
	 * 
	 * @param $mesg String    Message to pass to the next page.
	 * @param $uri  String    URI to redirect to.
	 * @param $type String    Can be notice, error, warning, etc.
	 * @param $status Integer Specific HTTP status.
	 */
	function flash($mesg, $uri='', $type=null, $status=null)
	{
		if (HTTP::$ajax)
		{
			if (!is_null($status)) {
				HTTP::status($status);
			}
			echo $mesg;
		}
		else
		{
			if (DEBUG < 2)
			{
				$this->Session->set_flash($mesg, $type);
				HTTP::redirect($uri, $status);
			}
			else
			{
				$this->set(array('url' => $uri, 'message' => $mesg));
				$this->render('', 'flash');
			}
		}
		exit;
	}
	
	/**
	 * Always called before executing any controller's action.
	 */
	protected function before_action() {}
	
	/**
	 * Always called after any controller's action has been executed.
	 */
	protected function after_action()  {}
}
?>